package cz.vse.javaee.album;

import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import java.lang.reflect.Field;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: 19.4.11
 * Time: 20:18
 * To change this template use File | Settings | File Templates.
 */
public class EntityFragmentManager {

    private final EntityManager em;

    public EntityFragmentManager(EntityManager em) {
        this.em = em;
    }

    public <F, K> F findFragment(Class<F> fragmentClass, K fragmentId) throws Exception {
        EntityFragment fragAnnot = fragmentClass.getAnnotation(EntityFragment.class);
        String readQuery = fragAnnot.read() + " where " + fragAnnot.id();

        Query q = em.createQuery(readQuery);
        Parameter<K> idParam = (Parameter<K>) q.getParameters().iterator().next();
        q.setParameter(idParam, fragmentId);

        Object[] entityAttrs = (Object[]) q.getSingleResult();

        F entityFragment = createFragmentInstance(fragmentClass, entityAttrs);

        return entityFragment;
    }

    private static <F> F createFragmentInstance(Class<F> fragmentClass, Object[] entityAttrs) throws InstantiationException, IllegalAccessException {
        F entityFragment;
        entityFragment = fragmentClass.newInstance();
        for (Field f : fragmentClass.getDeclaredFields()) {
            Index indAnnot = f.getAnnotation(Index.class);
            if (indAnnot != null) {
                f.setAccessible(true);
                f.set(entityFragment, entityAttrs[indAnnot.value()]);
            }
        }
        return entityFragment;
    }

    public <F> void updateFragment(F entityFragment) throws Exception {
        Class<?> fragmentClass = entityFragment.getClass();
        EntityFragment fragAnnot = fragmentClass.getAnnotation(EntityFragment.class);
        String writeQuery = fragAnnot.write() + " where " + fragAnnot.id();

        Query q = em.createQuery(writeQuery);
        for (Parameter<?> param: q.getParameters()) {
            String paramName = param.getName();
            Field f = fragmentClass.getDeclaredField(paramName);
            f.setAccessible(true);
            Object paramValue = f.get(entityFragment);
            q.setParameter(f.getName(), paramValue);
        }

        q.executeUpdate();
    }

    public <F> FragmentQuery<F> createQuery(Class<F> fragmentClass, String condition) {
        return new FragmentQuery<F>(fragmentClass, condition);
    }

    class FragmentQuery<F> {

        private final Query q;
        private final Class<F> fragmentClass;

        FragmentQuery(Class<F> fragmentClass, String condition) {
            this.fragmentClass = fragmentClass;

            EntityFragment fragAnnot = fragmentClass.getAnnotation(EntityFragment.class);
            String readQuery = fragAnnot.read() + " where " + condition;

            q = em.createQuery(readQuery);
        }

        public FragmentQuery setParameter(String paramName, Object value) {
            q.setParameter(paramName, value);
            return this;
        }

        public FragmentQuery setParameter(String paramName, Date value, TemporalType tt) {
            q.setParameter(paramName, value, tt);
            return this;
        }

        public FragmentQuery setFirstResult(int index) {
            q.setFirstResult(index);
            return this;
        }

        public FragmentQuery setMaxResults(int max) {
            q.setMaxResults(max);
            return this;
        }

        public Query getJPAQuery() {
            return q;
        }

        public <F> FragmentList<F> getResults() {
            List<Object[]> resultList = q.getResultList();
            return new FragmentList(resultList, fragmentClass);
        }

    }

    public static class FragmentList<F> implements List<F> {

        private final List<Object[]> resultList;
        private final Class<F> fragmentClass;

        public FragmentList(List<Object[]> resultList, Class<F> fragmentClass) {
            this.resultList = resultList;
            this.fragmentClass = fragmentClass;
        }

        public List<Object[]> getJPAResultList() {
            return resultList;
        }

        @Override
        public boolean isEmpty() {
            return resultList.isEmpty();
        }

        @Override
        public int size() {
            return resultList.size();
        }

        @Override
        public List<F> subList(int fromIndex, int toIndex) {
            return new FragmentList<F>(resultList.subList(fromIndex, toIndex), fragmentClass);
        }

        @Override
        public Iterator<F> iterator() {
            return new FragmentIterator();
        }

        class FragmentIterator implements Iterator<F> {

            private final Iterator<Object[]> resultIterator = resultList.iterator();

            @Override
            public boolean hasNext() {
                return resultIterator.hasNext();
            }

            @Override
            public F next() {
                try {
                    return (F)createFragmentInstance(fragmentClass, resultIterator.next());
                } catch (InstantiationException e) {
                    throw new IllegalStateException(e);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }

            @Override
            public void remove() {
                resultIterator.remove();
            }
        }

        @Override
        public boolean contains(Object o) {
            // todo
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public Object[] toArray() {
            //
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public <T> T[] toArray(T[] a) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean add(F f) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean containsAll(Collection<?> c) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean addAll(Collection<? extends F> c) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean addAll(int index, Collection<? extends F> c) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean removeAll(Collection<?> c) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public boolean retainAll(Collection<?> c) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public F get(int index) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public F set(int index, F element) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public void add(int index, F element) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public F remove(int index) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public int indexOf(Object o) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public int lastIndexOf(Object o) {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public ListIterator<F> listIterator() {
            throw new UnsupportedOperationException("todo");
        }

        @Override
        public ListIterator<F> listIterator(int index) {
            throw new UnsupportedOperationException("todo");
        }

    }

}
